﻿using Sustainalytics.ESGRatings.BusinessLogic.Computing.Exceptions;
using Sustainalytics.ESGRatings.BusinessLogic.Computing.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using static Sustainalytics.ESGRatings.BusinessLogic.Computing.IndicatorsConfiguration.IndicatorsConfigurationBuilder;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing
{

    public class IndicatorsConfiguration
    {
        private readonly Dictionary<string, int> _indicatorsCodesIndexes = new Dictionary<string, int>();

        protected IndicatorsConfiguration(string eTheme, string sTheme, string gTheme)
        {
            this.ETheme = eTheme.ToUpperInvariant();
            this.GTheme = gTheme.ToUpperInvariant();
            this.STheme = sTheme.ToUpperInvariant();
        }

        protected void Rebuild()
        {
            int i = 0;
            foreach (var code in Codes)
            {
                _indicatorsCodesIndexes[code] = i;
                i++;
            }

            EIndex = EsgMap.IndexOf(ETheme);
            GIndex = EsgMap.IndexOf(GTheme);
            SIndex = EsgMap.IndexOf(STheme);


        }

        /// <exception cref="UnknownIndicatorException">Indicator code was not found in templates set.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="code"/> is <see langword="null" />.</exception>
        public int GetIndex(string code)
        {

            if (string.IsNullOrEmpty(code))
            {
                throw new UnknownIndicatorException(code);
            }

            int index;
            if (!_indicatorsCodesIndexes.TryGetValue(code.ToUpperInvariant(), out index))
            {
                throw new UnknownIndicatorException(code);
            }


            return index;
        }

        public int Count
        {
            get
            {
                return Codes.Count;
            }
        }

        public byte DisjunctClustersCount
        {
            get;
            protected set;

        }
        //total number of clusters 1+theme numbers+clusters number
        public byte ClusterCount { get; protected set; }


        
        public List<IndicatorConfig> Indicators { get; protected set; }

        public List<string> Codes { get; protected set; }


        public byte[] ESGHeads { get; set; }

        public byte[] DisjunctClustersHeads { get; protected set; }

        public byte[] DisjunctClustersMap { get; protected set; }

        public List<string> EsgMap { get; protected set; }

        public List<string> ClustersMap { get; protected set; }

        public int GetAllClusterIndex()
        {
            return 0;
        }

        public string ETheme { get; protected set; }

        public string GTheme { get; protected set; }

        public string STheme { get; protected set; }

        public int EIndex
        {
            get;
            protected set;
        }

        public int GIndex
        {
            get;
            protected set;
        }

        public int SIndex
        {
            get;
            protected set;
        }


        public int DIndex => ClustersMap.IndexOf("D");


        public int QLPIndex => ClustersMap.IndexOf("QLP");


        public int PIndex => ClustersMap.IndexOf("P");


        public int QPIndex => ClustersMap.IndexOf("QP");


        public int GetClusterIndex(string cluster)
        {
            return ClustersMap.FindIndex(0, x => string.Equals(x, cluster, StringComparison.CurrentCultureIgnoreCase));
        }


        public string GetCluster(string indicatorCode)
        {
            int codeIndex = GetIndex(indicatorCode);

            var clusterIndex = -1;
            var iterator = 0;
            foreach (var i in DisjunctClustersHeads)
            {
                if (codeIndex < i)
                {
                    clusterIndex = iterator;
                    break;
                }

                iterator++;
            }

            if (clusterIndex == -1)
                clusterIndex = DisjunctClustersHeads.Length;

            return ClustersMap[DisjunctClustersMap[clusterIndex]];

        }

        //TODO: this should be refactored for governance separately or some other naming should be used
        public class IndicatorsConfigurationBuilder
        {
            readonly List<IndicatorConfig> _indicators = new List<IndicatorConfig>();
            readonly List<string> _themes = new List<string>();
            readonly List<string> _clusters = new List<string>();

            private readonly string _eTheme;
            private readonly string _sTheme;
            private readonly string _gTheme;


            public IndicatorsConfigurationBuilder(string ETheme, string STheme, string GTheme)
            {

                _eTheme = ETheme.ToUpperInvariant();
                _sTheme = STheme.ToUpperInvariant();
                _gTheme = GTheme.ToUpperInvariant();

            }

            public void AddIndicator(string code, string theme, string cluster)
            {
                if (!_themes.Contains(theme))
                    _themes.Add(theme);

                if (!_clusters.Contains(cluster))
                    _clusters.Add(cluster);

                var indicator = new IndicatorConfig
                {
                    ClusterId = (byte)_clusters.IndexOf(cluster),
                    Code = code,
                    ThemeId = _themes.IndexOf(theme),
                    Theme = theme,
                    Cluster = cluster
                };

                _indicators.Add(indicator);
            }

            public List<string> GetAvailableThemes()
            {
                return new List<string> { _eTheme, _sTheme, _gTheme };
            }

            public List<string> GetRegistredThmemes()
            {
                return _themes;
            }

            public List<IndicatorConfig> GetIndicators()
            {
                return _indicators.ToArray().ToList();
            }

            public IndicatorsConfiguration Build()
            {
                var themesHeads = new List<byte>();
                var themesMap = new List<string>();

                var disjunctClustersHeads = new List<byte>();
                var disjunctClustersMap = new List<byte>();

                var reorderedIndicators = ReorderIndicators();

                int previousTheme = -1;
                int previousCluster = -1;

                byte index = 0;

                themesMap.Add(_themes[reorderedIndicators[0].ThemeId]);
                disjunctClustersMap.Add(reorderedIndicators[0].ClusterId);

                foreach (var indicator in reorderedIndicators)
                {
                    var currentTheme = indicator.ThemeId;

                    if (previousTheme != -1)
                    {
                        if (currentTheme != previousTheme)
                        {
                            themesHeads.Add(index);
                            themesMap.Add(_themes[currentTheme]);
                        }
                    }

                    previousTheme = currentTheme;


                    var currentCluster = indicator.ClusterId;

                    if (previousCluster != -1)
                    {
                        if (previousCluster != currentCluster)
                        {
                            disjunctClustersHeads.Add(index);
                            disjunctClustersMap.Add(currentCluster);
                        }

                    }
                    previousCluster = currentCluster;

                    ++index;
                }


                var configurationResult = new IndicatorsConfiguration(_eTheme, _sTheme, _gTheme)
                {
                    ClustersMap = _clusters,
                    Indicators = reorderedIndicators,
                    Codes = reorderedIndicators.Select(i => i.Code).ToList(),
                    DisjunctClustersHeads = disjunctClustersHeads.ToArray(),
                    DisjunctClustersMap = disjunctClustersMap.ToArray(),
                    ESGHeads = themesHeads.IsNullOrEmpty() ? new[] { (byte)reorderedIndicators.Count } : themesHeads.ToArray(),
                    EsgMap = themesMap,
                    DisjunctClustersCount = (byte)disjunctClustersMap.Distinct().Count(),
                    ClusterCount = (byte)(1 + _themes.Count + disjunctClustersMap.Distinct().Count())
                };

                configurationResult.Rebuild();

                return configurationResult;



            }

            private List<IndicatorConfig> ReorderIndicators()
            {
                _indicators.Sort(new ThemeAndClusterComparer());

                var themeGroups = _indicators.GroupBy(i => i.ThemeId).ToList();
                var themesClusterGroups = new List<Dictionary<byte, IGrouping<byte, IndicatorConfig>>>();
                var clustersGroups = new List<Group>();

                foreach (var themeGroup in themeGroups)
                {
                    var groupByCluster = themeGroup.GroupBy(i => i.ClusterId).ToList();
                    themesClusterGroups.Add(groupByCluster.ToDictionary(v => v.Key));

                    clustersGroups.Add(new Group(groupByCluster.Select(g => (int)g.Key)));
                }


                ContinuousGroups alg = new ContinuousGroups();

                var themeIndex = 0;

                var reorderedIndicators = new List<IndicatorConfig>();

                foreach (var themesGroupsReorderedList in alg.GenerateCombinations(clustersGroups))
                {
                    var themeClustersDictionary = themesClusterGroups[themeIndex];

                    foreach (var clusterKey in themesGroupsReorderedList)
                    {
                        var clusterIndicators = themeClustersDictionary[(byte)clusterKey];
                        reorderedIndicators.AddRange(clusterIndicators);
                    }

                    themeIndex++;
                }
                return reorderedIndicators;
            }

            [DebuggerDisplay("Code = {Code}, Theme={Theme}, Cluster = {Cluster}")]
            public class IndicatorConfig
            {

                public string Code { get; set; }
                public int ThemeId { get; set; }

                public byte ClusterId { get; set; }

                public string Theme { get; set; }
                public string Cluster { get; set; }

            }

            public class ThemeAndClusterComparer : IComparer<IndicatorConfig>
            {
                public int Compare(IndicatorConfig x, IndicatorConfig y)
                {
                    var result = x.ThemeId.CompareTo(y.ThemeId);

                    if (result != 0)
                        return result;

                    return x.ClusterId.CompareTo(y.ClusterId);
                }
            }


            public static IndicatorsConfiguration GetHardcodedIndicators()
            {

                var builder = new IndicatorsConfigurationBuilder("E", "S", "G");

                #region indicators
                builder.AddIndicator("E.1.1", "E", "P");
                builder.AddIndicator("E.1.1.1", "E", "D");
                builder.AddIndicator("E.1.10", "E", "QNP");
                builder.AddIndicator("E.1.11", "E", "QNP");
                builder.AddIndicator("E.1.12", "E", "QLP");
                builder.AddIndicator("E.1.2", "E", "P");
                builder.AddIndicator("E.1.2.1", "E", "P");
                builder.AddIndicator("E.1.2.2", "E", "P");
                builder.AddIndicator("E.1.2.3", "E", "P");
                builder.AddIndicator("E.1.2.4", "E", "D");
                builder.AddIndicator("E.1.2.6", "E", "QNP");
                builder.AddIndicator("E.1.2.7", "E", "QNP");
                builder.AddIndicator("E.1.2.8", "E", "QNP");
                builder.AddIndicator("E.1.3", "E", "P");
                builder.AddIndicator("E.1.3.2", "E", "P");
                builder.AddIndicator("E.1.3.3", "E", "P");
                builder.AddIndicator("E.1.3.4", "E", "P");
                builder.AddIndicator("E.1.3.5", "E", "P");
                builder.AddIndicator("E.1.4", "E", "QNP");
                builder.AddIndicator("E.1.5", "E", "D");
                builder.AddIndicator("E.1.6", "E", "D");
                builder.AddIndicator("E.1.7", "E", "P");
                builder.AddIndicator("E.1.7.1", "E", "P");
                builder.AddIndicator("E.1.7.2", "E", "P");
                builder.AddIndicator("E.1.8", "E", "P");
                builder.AddIndicator("E.1.9", "E", "QNP");
                builder.AddIndicator("E.2.1", "E", "P");
                builder.AddIndicator("E.2.1.1", "E", "P");
                builder.AddIndicator("E.2.1.10", "E", "P");
                builder.AddIndicator("E.2.1.2", "E", "P");
                builder.AddIndicator("E.2.1.3", "E", "P");
                builder.AddIndicator("E.2.1.4", "E", "P");
                builder.AddIndicator("E.2.1.5", "E", "P");
                builder.AddIndicator("E.2.1.6", "E", "P");
                builder.AddIndicator("E.2.1.7", "E", "QNP");
                builder.AddIndicator("E.2.1.8", "E", "QNP");
                builder.AddIndicator("E.2.1.9", "E", "P");
                builder.AddIndicator("E.2.2", "E", "QLP");
                builder.AddIndicator("E.3.1.1", "E", "QNP");
                builder.AddIndicator("E.3.1.10", "E", "P");
                builder.AddIndicator("E.3.1.11", "E", "QNP");
                builder.AddIndicator("E.3.1.12", "E", "P");
                builder.AddIndicator("E.3.1.13", "E", "P");
                builder.AddIndicator("E.3.1.14", "E", "QNP");
                builder.AddIndicator("E.3.1.15", "E", "QNP");
                builder.AddIndicator("E.3.1.16", "E", "QNP");
                builder.AddIndicator("E.3.1.17", "E", "QNP");
                builder.AddIndicator("E.3.1.2", "E", "QNP");
                builder.AddIndicator("E.3.1.3", "E", "QNP");
                builder.AddIndicator("E.3.1.4", "E", "QNP");
                builder.AddIndicator("E.3.1.5", "E", "QNP");
                builder.AddIndicator("E.3.1.6", "E", "P");
                builder.AddIndicator("E.3.1.7", "E", "P");
                builder.AddIndicator("E.3.1.8", "E", "QNP");
                builder.AddIndicator("E.3.1.9", "E", "P");
                builder.AddIndicator("E.3.2", "E", "QLP");
                builder.AddIndicator("G.1.1", "G", "P");
                builder.AddIndicator("G.1.1.1", "G", "P");
                builder.AddIndicator("G.1.2", "G", "P");
                builder.AddIndicator("G.1.3", "G", "P");
                builder.AddIndicator("G.1.3.1", "G", "P");
                builder.AddIndicator("G.1.3.2", "G", "P");
                builder.AddIndicator("G.1.3.3", "G", "P");
                builder.AddIndicator("G.1.3.4", "G", "P");
                builder.AddIndicator("G.1.3.5", "G", "P");
                builder.AddIndicator("G.1.4", "G", "D");
                builder.AddIndicator("G.1.4.1", "G", "P");
                builder.AddIndicator("G.1.4.3", "G", "P");
                builder.AddIndicator("G.1.4.4", "G", "P");
                builder.AddIndicator("G.1.4.5", "G", "P");
                builder.AddIndicator("G.1.4.6", "G", "P");
                builder.AddIndicator("G.1.5", "G", "QLP");
                builder.AddIndicator("G.2.1", "G", "D");
                builder.AddIndicator("G.2.10", "G", "P");
                builder.AddIndicator("G.2.11", "G", "P");
                builder.AddIndicator("G.2.12", "G", "P");
                builder.AddIndicator("G.2.13", "G", "QLP");
                builder.AddIndicator("G.2.2", "G", "D");
                builder.AddIndicator("G.2.3", "G", "D");
                builder.AddIndicator("G.2.4", "G", "D");
                builder.AddIndicator("G.2.5", "G", "P");
                builder.AddIndicator("G.2.5.1", "G", "P");
                builder.AddIndicator("G.2.6", "G", "P");
                builder.AddIndicator("G.2.7", "G", "P");
                builder.AddIndicator("G.2.8", "G", "P");
                builder.AddIndicator("G.2.9", "G", "P");
                builder.AddIndicator("G.3.1", "G", "P");
                builder.AddIndicator("G.3.2", "G", "P");
                builder.AddIndicator("G.3.3.1", "G", "D");
                builder.AddIndicator("G.3.4", "G", "QLP");
                builder.AddIndicator("S.1.1", "S", "P");
                builder.AddIndicator("S.1.1.1", "S", "P");
                builder.AddIndicator("S.1.2", "S", "P");
                builder.AddIndicator("S.1.3", "S", "P");
                builder.AddIndicator("S.1.4", "S", "QNP");
                builder.AddIndicator("S.1.5", "S", "QNP");
                builder.AddIndicator("S.1.5.1", "S", "QNP");
                builder.AddIndicator("S.1.6", "S", "QNP");
                builder.AddIndicator("S.1.6.1", "S", "QNP");
                builder.AddIndicator("S.1.6.2", "S", "P");
                builder.AddIndicator("S.1.6.3", "S", "P");
                builder.AddIndicator("S.1.6.4", "S", "P");
                builder.AddIndicator("S.1.6.5", "S", "QNP");
                builder.AddIndicator("S.1.6.6", "S", "QNP");
                builder.AddIndicator("S.1.7", "S", "QLP");
                builder.AddIndicator("S.2.1", "S", "P");
                builder.AddIndicator("S.2.1.1", "S", "P");
                builder.AddIndicator("S.2.1.2", "S", "P");
                builder.AddIndicator("S.2.1.3", "S", "P");
                builder.AddIndicator("S.2.1.3.1", "S", "P");
                builder.AddIndicator("S.2.2", "S", "P");
                builder.AddIndicator("S.2.2.1", "S", "P");
                builder.AddIndicator("S.2.2.2", "S", "D");
                builder.AddIndicator("S.2.2.2.1", "S", "P");
                builder.AddIndicator("S.2.2.3", "S", "P");
                builder.AddIndicator("S.2.2.4", "S", "QNP");
                builder.AddIndicator("S.2.3", "S", "QLP");
                builder.AddIndicator("S.3.1.1", "S", "P");
                builder.AddIndicator("S.3.1.10", "S", "P");
                builder.AddIndicator("S.3.1.11", "S", "P");
                builder.AddIndicator("S.3.1.12", "S", "P");
                builder.AddIndicator("S.3.1.2", "S", "P");
                builder.AddIndicator("S.3.1.3", "S", "P");
                builder.AddIndicator("S.3.1.4", "S", "P");
                builder.AddIndicator("S.3.1.5", "S", "P");
                builder.AddIndicator("S.3.1.6", "S", "P");
                builder.AddIndicator("S.3.1.7", "S", "P");
                builder.AddIndicator("S.3.1.8", "S", "QNP");
                builder.AddIndicator("S.3.1.9", "S", "P");
                builder.AddIndicator("S.3.2.1", "S", "P");
                builder.AddIndicator("S.3.3", "S", "QLP");
                builder.AddIndicator("S.4.1", "S", "QNP");
                builder.AddIndicator("S.4.2.1", "S", "P");
                builder.AddIndicator("S.4.2.10", "S", "P");
                builder.AddIndicator("S.4.2.11", "S", "P");
                builder.AddIndicator("S.4.2.12", "S", "P");
                builder.AddIndicator("S.4.2.13", "S", "P");
                builder.AddIndicator("S.4.2.14", "S", "QNP");
                builder.AddIndicator("S.4.2.2", "S", "P");
                builder.AddIndicator("S.4.2.3", "S", "P");
                builder.AddIndicator("S.4.2.4", "S", "P");
                builder.AddIndicator("S.4.2.5", "S", "P");
                builder.AddIndicator("S.4.2.6", "S", "P");
                builder.AddIndicator("S.4.2.7", "S", "P");
                builder.AddIndicator("S.4.2.8", "S", "P");
                builder.AddIndicator("S.4.2.9", "S", "P");
                builder.AddIndicator("S.4.3", "S", "QLP");
                builder.AddIndicator("S.5.1", "S", "P");
                builder.AddIndicator("S.5.2", "S", "P");
                builder.AddIndicator("S.5.3", "S", "QNP");

                #endregion

                return builder.Build();

            }


        }

    }

}
